home *** CD-ROM | disk | FTP | other *** search
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
- /* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is the Mozilla Tree Panel.
- *
- * The Initial Developer of the Original Code is
- * The Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2011
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Rob Campbell <rcampbell@mozilla.com> (original author)
- * Mihai ╚ÿucan <mihai.sucan@gmail.com>
- * Julian Viereck <jviereck@mozilla.com>
- * Paul Rouget <paul@mozilla.com>
- * Kyle Simpson <getify@mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
- const Cu = Components.utils;
-
- Cu.import("resource:///modules/domplate.jsm");
- Cu.import("resource:///modules/InsideOutBox.jsm");
- Cu.import("resource://gre/modules/Services.jsm");
-
- var EXPORTED_SYMBOLS = ["TreePanel", "DOMHelpers"];
-
- const INSPECTOR_URI = "chrome://browser/content/inspector.html";
-
- /**
- * TreePanel
- * A container for the Inspector's HTML Tree Panel widget constructor function.
- * @param aContext nsIDOMWindow (xulwindow)
- * @param aIUI global InspectorUI object
- */
- function TreePanel(aContext, aIUI) {
- this._init(aContext, aIUI);
- };
-
- TreePanel.prototype = {
- showTextNodesWithWhitespace: false,
- id: "treepanel", // DO NOT LOCALIZE
- openInDock: true,
-
- /**
- * The tree panel container element.
- * @returns xul:panel|xul:vbox|null
- * xul:panel is returned when the tree panel is not docked, or
- * xul:vbox when when the tree panel is docked.
- * null is returned when no container is available.
- */
- get container()
- {
- if (this.openInDock) {
- return this.document.getElementById("inspector-tree-box");
- }
-
- return this.document.getElementById("inspector-tree-panel");
- },
-
- /**
- * Main TreePanel boot-strapping method. Initialize the TreePanel with the
- * originating context and the InspectorUI global.
- * @param aContext nsIDOMWindow (xulwindow)
- * @param aIUI global InspectorUI object
- */
- _init: function TP__init(aContext, aIUI)
- {
- this.IUI = aIUI;
- this.window = aContext;
- this.document = this.window.document;
-
- domplateUtils.setDOM(this.window);
-
- this.DOMHelpers = new DOMHelpers(this.window);
-
- let isOpen = this.isOpen.bind(this);
-
- this.registrationObject = {
- id: this.id,
- label: this.IUI.strings.GetStringFromName("htmlPanel.label"),
- tooltiptext: this.IUI.strings.GetStringFromName("htmlPanel.tooltiptext"),
- accesskey: this.IUI.strings.GetStringFromName("htmlPanel.accesskey"),
- context: this,
- get isOpen() isOpen(),
- show: this.open,
- hide: this.close,
- onSelect: this.select,
- panel: this.openInDock ? null : this.container,
- unregister: this.destroy,
- };
- this.editingEvents = {};
-
- if (!this.openInDock) {
- this._boundClose = this.close.bind(this);
- this.container.addEventListener("popuphiding", this._boundClose, false);
- }
-
- // Register the HTML panel with the highlighter
- this.IUI.registerTool(this.registrationObject);
- },
-
- /**
- * Initialization function for the TreePanel.
- */
- initializeIFrame: function TP_initializeIFrame()
- {
- if (!this.initializingTreePanel || this.treeLoaded) {
- return;
- }
- this.treeBrowserDocument = this.treeIFrame.contentDocument;
- this.treePanelDiv = this.treeBrowserDocument.createElement("div");
- this.treeBrowserDocument.body.appendChild(this.treePanelDiv);
- this.treePanelDiv.ownerPanel = this;
- this.ioBox = new InsideOutBox(this, this.treePanelDiv);
- this.ioBox.createObjectBox(this.IUI.win.document.documentElement);
- this.treeLoaded = true;
- this.treeIFrame.addEventListener("click", this.onTreeClick.bind(this), false);
- this.treeIFrame.addEventListener("dblclick", this.onTreeDblClick.bind(this), false);
- this.treeIFrame.addEventListener("keypress", this.IUI, false);
- delete this.initializingTreePanel;
- Services.obs.notifyObservers(null,
- this.IUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, null);
- if (this.IUI.selection)
- this.select(this.IUI.selection, true);
- },
-
- /**
- * Open the inspector's tree panel and initialize it.
- */
- open: function TP_open()
- {
- if (this.initializingTreePanel && !this.treeLoaded) {
- return;
- }
-
- this.initializingTreePanel = true;
- if (!this.openInDock)
- this.container.hidden = false;
-
- this.treeIFrame = this.document.getElementById("inspector-tree-iframe");
- if (!this.treeIFrame) {
- this.treeIFrame = this.document.createElement("iframe");
- this.treeIFrame.setAttribute("id", "inspector-tree-iframe");
- this.treeIFrame.flex = 1;
- this.treeIFrame.setAttribute("type", "content");
- }
-
- if (this.openInDock) { // Create vbox
- this.openDocked();
- return;
- }
-
- let resizerBox = this.document.getElementById("tree-panel-resizer-box");
- this.treeIFrame = this.container.insertBefore(this.treeIFrame, resizerBox);
-
- let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
- {
- this.treeIFrame.removeEventListener("load",
- boundLoadedInitializeTreePanel, true);
- this.initializeIFrame();
- }.bind(this);
-
- let boundTreePanelShown = function treePanelShown()
- {
- this.container.removeEventListener("popupshown",
- boundTreePanelShown, false);
-
- this.treeIFrame.addEventListener("load",
- boundLoadedInitializeTreePanel, true);
-
- let src = this.treeIFrame.getAttribute("src");
- if (src != INSPECTOR_URI) {
- this.treeIFrame.setAttribute("src", INSPECTOR_URI);
- } else {
- this.treeIFrame.contentWindow.location.reload();
- }
- }.bind(this);
-
- this.container.addEventListener("popupshown", boundTreePanelShown, false);
-
- const panelWidthRatio = 7 / 8;
- const panelHeightRatio = 1 / 5;
-
- let width = parseInt(this.IUI.win.outerWidth * panelWidthRatio);
- let height = parseInt(this.IUI.win.outerHeight * panelHeightRatio);
- let y = Math.min(this.document.defaultView.screen.availHeight - height,
- this.IUI.win.innerHeight);
-
- this.container.openPopup(this.browser, "overlap", 0, 0,
- false, false);
-
- this.container.moveTo(80, y);
- this.container.sizeTo(width, height);
- },
-
- openDocked: function TP_openDocked()
- {
- let treeBox = null;
- let toolbar = this.IUI.toolbar.nextSibling; // Addons bar, typically
- let toolbarParent =
- this.IUI.browser.ownerDocument.getElementById("browser-bottombox");
- treeBox = this.document.createElement("vbox");
- treeBox.id = "inspector-tree-box";
- treeBox.state = "open"; // for the registerTools API.
- try {
- treeBox.height =
- Services.prefs.getIntPref("devtools.inspector.htmlHeight");
- } catch(e) {
- treeBox.height = 112;
- }
-
- treeBox.minHeight = 64;
- treeBox.flex = 1;
- toolbarParent.insertBefore(treeBox, toolbar);
-
- this.IUI.toolbar.setAttribute("treepanel-open", "true");
-
- treeBox.appendChild(this.treeIFrame);
-
- let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
- {
- this.treeIFrame.removeEventListener("load",
- boundLoadedInitializeTreePanel, true);
- this.initializeIFrame();
- }.bind(this);
-
- this.treeIFrame.addEventListener("load",
- boundLoadedInitializeTreePanel, true);
-
- let src = this.treeIFrame.getAttribute("src");
- if (src != INSPECTOR_URI) {
- this.treeIFrame.setAttribute("src", INSPECTOR_URI);
- } else {
- this.treeIFrame.contentWindow.location.reload();
- }
- },
-
- /**
- * Close the TreePanel.
- */
- close: function TP_close()
- {
- if (this.openInDock) {
- this.IUI.toolbar.removeAttribute("treepanel-open");
-
- let treeBox = this.container;
- Services.prefs.setIntPref("devtools.inspector.htmlHeight", treeBox.height);
- let treeBoxParent = treeBox.parentNode;
- treeBoxParent.removeChild(treeBox);
- } else {
- this.container.hidePopup();
- }
-
- if (this.treePanelDiv) {
- this.treePanelDiv.ownerPanel = null;
- let parent = this.treePanelDiv.parentNode;
- parent.removeChild(this.treePanelDiv);
- delete this.treePanelDiv;
- delete this.treeBrowserDocument;
- }
-
- this.treeLoaded = false;
- },
-
- /**
- * Is the TreePanel open?
- * @returns boolean
- */
- isOpen: function TP_isOpen()
- {
- if (this.openInDock)
- return this.treeLoaded && this.container;
-
- return this.treeLoaded && this.container.state == "open";
- },
-
- /**
- * Create the ObjectBox for the given object.
- * @param object nsIDOMNode
- * @param isRoot boolean - Is this the root object?
- * @returns InsideOutBox
- */
- createObjectBox: function TP_createObjectBox(object, isRoot)
- {
- let tag = domplateUtils.getNodeTag(object);
- if (tag)
- return tag.replace({object: object}, this.treeBrowserDocument);
- },
-
- getParentObject: function TP_getParentObject(node)
- {
- return this.DOMHelpers.getParentObject(node);
- },
-
- getChildObject: function TP_getChildObject(node, index, previousSibling)
- {
- return this.DOMHelpers.getChildObject(node, index, previousSibling,
- this.showTextNodesWithWhitespace);
- },
-
- getFirstChild: function TP_getFirstChild(node)
- {
- return this.DOMHelpers.getFirstChild(node);
- },
-
- getNextSibling: function TP_getNextSibling(node)
- {
- return this.DOMHelpers.getNextSibling(node);
- },
-
- /////////////////////////////////////////////////////////////////////
- // Event Handling
-
- /**
- * Handle click events in the html tree panel.
- * @param aEvent
- * The mouse event.
- */
- onTreeClick: function TP_onTreeClick(aEvent)
- {
- let node;
- let target = aEvent.target;
- let hitTwisty = false;
- if (this.hasClass(target, "twisty")) {
- node = this.getRepObject(aEvent.target.nextSibling);
- hitTwisty = true;
- } else {
- node = this.getRepObject(aEvent.target);
- }
-
- if (node) {
- if (hitTwisty) {
- this.ioBox.toggleObject(node);
- } else {
- if (this.IUI.inspecting) {
- this.IUI.stopInspecting(true);
- } else {
- this.IUI.select(node, true, false);
- this.IUI.highlighter.highlightNode(node);
- }
- }
- }
- },
-
- /**
- * Handle double-click events in the html tree panel.
- * (double-clicking an attribute value allows it to be edited)
- * @param aEvent
- * The mouse event.
- */
- onTreeDblClick: function TP_onTreeDblClick(aEvent)
- {
- // if already editing an attribute value, double-clicking elsewhere
- // in the tree is the same as a click, which dismisses the editor
- if (this.editingContext)
- this.closeEditor();
-
- let target = aEvent.target;
-
- if (this.hasClass(target, "nodeValue")) {
- let repObj = this.getRepObject(target);
- let attrName = target.getAttribute("data-attributeName");
- let attrVal = target.innerHTML;
-
- this.editAttributeValue(target, repObj, attrName, attrVal);
- }
- },
-
- /**
- * Starts the editor for an attribute value.
- * @param aAttrObj
- * The DOM object representing the attribute value in the HTML Tree
- * @param aRepObj
- * The original DOM (target) object being inspected/edited
- * @param aAttrName
- * The name of the attribute being edited
- * @param aAttrVal
- * The current value of the attribute being edited
- */
- editAttributeValue:
- function TP_editAttributeValue(aAttrObj, aRepObj, aAttrName, aAttrVal)
- {
- let editor = this.treeBrowserDocument.getElementById("attribute-editor");
- let editorInput =
- this.treeBrowserDocument.getElementById("attribute-editor-input");
- let attrDims = aAttrObj.getBoundingClientRect();
- // figure out actual viewable viewport dimensions (sans scrollbars)
- let viewportWidth = this.treeBrowserDocument.documentElement.clientWidth;
- let viewportHeight = this.treeBrowserDocument.documentElement.clientHeight;
-
- // saves the editing context for use when the editor is saved/closed
- this.editingContext = {
- attrObj: aAttrObj,
- repObj: aRepObj,
- attrName: aAttrName
- };
-
- // highlight attribute-value node in tree while editing
- this.addClass(aAttrObj, "editingAttributeValue");
-
- // show the editor
- this.addClass(editor, "editing");
-
- // offset the editor below the attribute-value node being edited
- let editorVeritcalOffset = 2;
-
- // keep the editor comfortably within the bounds of the viewport
- let editorViewportBoundary = 5;
-
- // outer editor is sized based on the <input> box inside it
- editorInput.style.width = Math.min(attrDims.width, viewportWidth -
- editorViewportBoundary) + "px";
- editorInput.style.height = Math.min(attrDims.height, viewportHeight -
- editorViewportBoundary) + "px";
- let editorDims = editor.getBoundingClientRect();
-
- // calculate position for the editor according to the attribute node
- let editorLeft = attrDims.left + this.treeIFrame.contentWindow.scrollX -
- // center the editor against the attribute value
- ((editorDims.width - attrDims.width) / 2);
- let editorTop = attrDims.top + this.treeIFrame.contentWindow.scrollY +
- attrDims.height + editorVeritcalOffset;
-
- // but, make sure the editor stays within the visible viewport
- editorLeft = Math.max(0, Math.min(
- (this.treeIFrame.contentWindow.scrollX +
- viewportWidth - editorDims.width),
- editorLeft)
- );
- editorTop = Math.max(0, Math.min(
- (this.treeIFrame.contentWindow.scrollY +
- viewportHeight - editorDims.height),
- editorTop)
- );
-
- // position the editor
- editor.style.left = editorLeft + "px";
- editor.style.top = editorTop + "px";
-
- // set and select the text
- editorInput.value = aAttrVal;
- editorInput.select();
-
- // remove tree key navigation events
- this.treeIFrame.removeEventListener("keypress", this.IUI, false);
-
- // listen for editor specific events
- this.bindEditorEvent(editor, "click", function(aEvent) {
- aEvent.stopPropagation();
- });
- this.bindEditorEvent(editor, "dblclick", function(aEvent) {
- aEvent.stopPropagation();
- });
- this.bindEditorEvent(editor, "keypress",
- this.handleEditorKeypress.bind(this));
-
- // event notification
- Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED,
- null);
- },
-
- /**
- * Handle binding an event handler for the editor.
- * (saves the callback for easier unbinding later)
- * @param aEditor
- * The DOM object for the editor
- * @param aEventName
- * The name of the event to listen for
- * @param aEventCallback
- * The callback to bind to the event (and also to save for later
- * unbinding)
- */
- bindEditorEvent:
- function TP_bindEditorEvent(aEditor, aEventName, aEventCallback)
- {
- this.editingEvents[aEventName] = aEventCallback;
- aEditor.addEventListener(aEventName, aEventCallback, false);
- },
-
- /**
- * Handle unbinding an event handler from the editor.
- * (unbinds the previously bound and saved callback)
- * @param aEditor
- * The DOM object for the editor
- * @param aEventName
- * The name of the event being listened for
- */
- unbindEditorEvent: function TP_unbindEditorEvent(aEditor, aEventName)
- {
- aEditor.removeEventListener(aEventName, this.editingEvents[aEventName],
- false);
- this.editingEvents[aEventName] = null;
- },
-
- /**
- * Handle keypress events in the editor.
- * @param aEvent
- * The keyboard event.
- */
- handleEditorKeypress: function TP_handleEditorKeypress(aEvent)
- {
- if (aEvent.which == this.window.KeyEvent.DOM_VK_RETURN) {
- this.saveEditor();
- aEvent.preventDefault();
- aEvent.stopPropagation();
- } else if (aEvent.keyCode == this.window.KeyEvent.DOM_VK_ESCAPE) {
- this.closeEditor();
- aEvent.preventDefault();
- aEvent.stopPropagation();
- }
- },
-
- /**
- * Close the editor and cleanup.
- */
- closeEditor: function TP_closeEditor()
- {
- let editor = this.treeBrowserDocument.getElementById("attribute-editor");
- let editorInput =
- this.treeBrowserDocument.getElementById("attribute-editor-input");
-
- // remove highlight from attribute-value node in tree
- this.removeClass(this.editingContext.attrObj, "editingAttributeValue");
-
- // hide editor
- this.removeClass(editor, "editing");
-
- // stop listening for editor specific events
- this.unbindEditorEvent(editor, "click");
- this.unbindEditorEvent(editor, "dblclick");
- this.unbindEditorEvent(editor, "keypress");
-
- // clean up after the editor
- editorInput.value = "";
- editorInput.blur();
- this.editingContext = null;
- this.editingEvents = {};
-
- // re-add navigation listener
- this.treeIFrame.addEventListener("keypress", this.IUI, false);
-
- // event notification
- Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED,
- null);
- },
-
- /**
- * Commit the edits made in the editor, then close it.
- */
- saveEditor: function TP_saveEditor()
- {
- let editorInput =
- this.treeBrowserDocument.getElementById("attribute-editor-input");
-
- // set the new attribute value on the original target DOM element
- this.editingContext.repObj.setAttribute(this.editingContext.attrName,
- editorInput.value);
-
- // update the HTML tree attribute value
- this.editingContext.attrObj.innerHTML = editorInput.value;
-
- this.IUI.isDirty = true;
- this.IUI.nodeChanged(this.registrationObject);
-
- // event notification
- Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED,
- null);
-
- this.closeEditor();
- },
-
- /**
- * Simple tree select method.
- * @param aNode the DOM node in the content document to select.
- * @param aScroll boolean scroll to the visible node?
- */
- select: function TP_select(aNode, aScroll)
- {
- if (this.ioBox)
- this.ioBox.select(aNode, true, true, aScroll);
- },
-
- ///////////////////////////////////////////////////////////////////////////
- //// Utility functions
-
- /**
- * Does the given object have a class attribute?
- * @param aNode
- * the DOM node.
- * @param aClass
- * The class string.
- * @returns boolean
- */
- hasClass: function TP_hasClass(aNode, aClass)
- {
- if (!(aNode instanceof this.window.Element))
- return false;
- return aNode.classList.contains(aClass);
- },
-
- /**
- * Add the class name to the given object.
- * @param aNode
- * the DOM node.
- * @param aClass
- * The class string.
- */
- addClass: function TP_addClass(aNode, aClass)
- {
- if (aNode instanceof this.window.Element)
- aNode.classList.add(aClass);
- },
-
- /**
- * Remove the class name from the given object
- * @param aNode
- * the DOM node.
- * @param aClass
- * The class string.
- */
- removeClass: function TP_removeClass(aNode, aClass)
- {
- if (aNode instanceof this.window.Element)
- aNode.classList.remove(aClass);
- },
-
- /**
- * Get the "repObject" from the HTML panel's domplate-constructed DOM node.
- * In this system, a "repObject" is the Object being Represented by the box
- * object. It is the "real" object that we're building our facade around.
- *
- * @param element
- * The element in the HTML panel the user clicked.
- * @returns either a real node or null
- */
- getRepObject: function TP_getRepObject(element)
- {
- let target = null;
- for (let child = element; child; child = child.parentNode) {
- if (this.hasClass(child, "repTarget"))
- target = child;
-
- if (child.repObject) {
- if (!target && this.hasClass(child.repObject, "repIgnore"))
- break;
- else
- return child.repObject;
- }
- }
- return null;
- },
-
- /**
- * Destructor function. Cleanup.
- */
- destroy: function TP_destroy()
- {
- if (this.isOpen()) {
- this.close();
- }
-
- domplateUtils.setDOM(null);
-
- if (this.DOMHelpers) {
- this.DOMHelpers.destroy();
- delete this.DOMHelpers;
- }
-
- if (this.treePanelDiv) {
- this.treePanelDiv.ownerPanel = null;
- let parent = this.treePanelDiv.parentNode;
- parent.removeChild(this.treePanelDiv);
- delete this.treePanelDiv;
- delete this.treeBrowserDocument;
- }
-
- if (this.treeIFrame) {
- this.treeIFrame.removeEventListener("keypress", this.IUI, false);
- this.treeIFrame.removeEventListener("dblclick", this.onTreeDblClick, false);
- this.treeIFrame.removeEventListener("click", this.onTreeClick, false);
- let parent = this.treeIFrame.parentNode;
- parent.removeChild(this.treeIFrame);
- delete this.treeIFrame;
- }
-
- if (this.ioBox) {
- this.ioBox.destroy();
- delete this.ioBox;
- }
-
- if (!this.openInDock) {
- this.container.removeEventListener("popuphiding", this._boundClose, false);
- delete this._boundClose;
- }
- }
- };
-
-
- /**
- * DOMHelpers
- * Makes DOM traversal easier. Goes through iframes.
- *
- * @constructor
- * @param nsIDOMWindow aWindow
- * The content window, owning the document to traverse.
- */
- function DOMHelpers(aWindow) {
- this.window = aWindow;
- };
-
- DOMHelpers.prototype = {
- getParentObject: function Helpers_getParentObject(node)
- {
- let parentNode = node ? node.parentNode : null;
-
- if (!parentNode) {
- // Documents have no parentNode; Attr, Document, DocumentFragment, Entity,
- // and Notation. top level windows have no parentNode
- if (node && node == this.window.Node.DOCUMENT_NODE) {
- // document type
- if (node.defaultView) {
- let embeddingFrame = node.defaultView.frameElement;
- if (embeddingFrame)
- return embeddingFrame.parentNode;
- }
- }
- // a Document object without a parentNode or window
- return null; // top level has no parent
- }
-
- if (parentNode.nodeType == this.window.Node.DOCUMENT_NODE) {
- if (parentNode.defaultView) {
- return parentNode.defaultView.frameElement;
- }
- // parent is document element, but no window at defaultView.
- return null;
- }
-
- if (!parentNode.localName)
- return null;
-
- return parentNode;
- },
-
- getChildObject: function Helpers_getChildObject(node, index, previousSibling,
- showTextNodesWithWhitespace)
- {
- if (!node)
- return null;
-
- if (node.contentDocument) {
- // then the node is a frame
- if (index == 0) {
- return node.contentDocument.documentElement; // the node's HTMLElement
- }
- return null;
- }
-
- if (node instanceof this.window.GetSVGDocument) {
- let svgDocument = node.getSVGDocument();
- if (svgDocument) {
- // then the node is a frame
- if (index == 0) {
- return svgDocument.documentElement; // the node's SVGElement
- }
- return null;
- }
- }
-
- let child = null;
- if (previousSibling) // then we are walking
- child = this.getNextSibling(previousSibling);
- else
- child = this.getFirstChild(node);
-
- if (showTextNodesWithWhitespace)
- return child;
-
- for (; child; child = this.getNextSibling(child)) {
- if (!this.isWhitespaceText(child))
- return child;
- }
-
- return null; // we have no children worth showing.
- },
-
- getFirstChild: function Helpers_getFirstChild(node)
- {
- let SHOW_ALL = Components.interfaces.nsIDOMNodeFilter.SHOW_ALL;
- this.treeWalker = node.ownerDocument.createTreeWalker(node,
- SHOW_ALL, null, false);
- return this.treeWalker.firstChild();
- },
-
- getNextSibling: function Helpers_getNextSibling(node)
- {
- let next = this.treeWalker.nextSibling();
-
- if (!next)
- delete this.treeWalker;
-
- return next;
- },
-
- isWhitespaceText: function Helpers_isWhitespaceText(node)
- {
- return node.nodeType == this.window.Node.TEXT_NODE &&
- !/[^\s]/.exec(node.nodeValue);
- },
-
- destroy: function Helpers_destroy()
- {
- delete this.window;
- delete this.treeWalker;
- }
- };
-